/*
 * Copyright (C) 2014 Lucid Wolf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.lucidhex.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *  
 * @author Lucid Wolf
 */
public class Reflection {
    private Set<Class> classes = new HashSet<>();
    private Set<String> svgImages = new HashSet<>();
    private String jarPackagePath = "";
    private String dirPackagePath = "";
    public Reflection(String s) {
        getClassesForPackage(s);
    }

    /**
     * Scans all classloaders for the current thread for loaded jars, and then scans
     * each jar for the package name in question, listing all classes directly under
     * the package name in question. Assumes directory structure in jar file and class
     * package naming follow java conventions (i.e. com.example.test.MyTest would be in
     * /com/example/test/MyTest.class)
     */
    private void getClassesForPackage(String packageName){
        
      jarPackagePath = packageName.replace(".", "/");
      dirPackagePath = packageName.replace(".", "\\");
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      Set<URL> jarUrls = new HashSet<>();
      Set<URL> classUrls = new HashSet<>();

      while (classLoader != null) {
        if (classLoader instanceof URLClassLoader) {
              for (URL url : ((URLClassLoader) classLoader).getURLs()) {
                  if (url.getFile().endsWith(".jar")) {
                      jarUrls.add(url);
                  }else{
                      File dir = new File(url.getFile());
                    if (dir.isDirectory()) {
                        classUrls.add(url);
                      }                      
                  }
              }
          }

        classLoader = classLoader.getParent();
      }
      for (URL url : classUrls){
        File dir = new File(url.getFile());
          nestedDirRead(dir, dir.getAbsolutePath().length());
      }

      for (URL url : jarUrls) {
        JarInputStream jarStream = null; 
        InputStream stream = null;
            try {
                stream = url.openStream();
                jarStream = new JarInputStream(stream); // may want better way to open url connections
            } catch (IOException ex) {
                Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
            }
        JarEntry entry = null;
            try {
                entry = jarStream.getNextJarEntry();
            } catch (IOException ex) {
                Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
            }
        try {
            while (entry != null) {
              String name = entry.getName();
              int i = name.lastIndexOf("/");

              if (name.endsWith(".class") && name.contains(jarPackagePath)) {
                    try {
                        classes.add(Class.forName(name.substring(0, name.length() - 6).replace("/", ".")));
                    } catch (ClassNotFoundException ex) {
                        Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
                    }
              } else if (name.endsWith(".svg") && name.contains(jarPackagePath)) {
                  String svgXML = new Scanner(stream).useDelimiter("\\A").next();
                  svgImages.add(svgXML);
              }
              
                       entry = jarStream.getNextJarEntry();
            }
        jarStream.close();
        } catch (IOException ex) {
            Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
        }
      } 
    }

    public Set<Class> getSubTypesOf(Class aClass) {
        HashSet<Class> out = new HashSet<>();
        for(Class c : classes){
            if(aClass.isAssignableFrom(c) &&  !Modifier.isAbstract(c.getModifiers()) && !Modifier.isInterface(c.getModifiers())){
                out.add(c);
            }
        }
        return out;
    }

    private void nestedDirRead(File f, int cap){
        if(f.isDirectory()){
            for(File fi : f.listFiles()){
                nestedDirRead(fi, cap);
            }
        }else if(f.getName().contains(".class") && f.getPath().contains(dirPackagePath)) {
            try {
                String name = f.getAbsolutePath().substring(cap+1);
                name = name.substring(0, name.length() - 6).replace("\\", ".");
                classes.add(Class.forName(name));
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
            }
        }else if (f.getName().endsWith(".svg") && f.getPath().contains(dirPackagePath)) {
                 
                  String svgXML;
            try {
                svgXML = new Scanner(f).useDelimiter("\\A").next();
                svgImages.add(svgXML);
           } catch (FileNotFoundException ex) {
                Logger.getLogger(Reflection.class.getName()).log(Level.SEVERE, null, ex);
            }              
        }
    }
}
